package uk.ac.nott.cs.g53dia.bee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import meadow.MeadowWrapper;
import replay.Player;
import replay.Recorder;
import uk.ac.nott.cs.g53dia.Bee;
import uk.ac.nott.cs.g53dia.BeeViewer;
import uk.ac.nott.cs.g53dia.Environment;
import utils.RandomGenerator;
import utils.Simulation;
import valueobj.GlobalState;
import valueobj.SimulationSettings;

public abstract class Simulator {

  private static volatile boolean PAUSED = false;

  /**
   * Time for which execution pauses so that GUI can update. Reducing this value
   * causes the simulation to run faster.
   */
  private volatile int delay;

  /**
   * Number of days to execute
   */
  private volatile int duration;

  /**
   * Number of bees
   */
  private int beeNum;
  private List<Bee> swarm;
  private Environment env;
  private BeeViewer bv;
  private Recorder recorder;
  private Player player;
  private boolean recording;
  private boolean playing;
  private GlobalState globalState;
  private MeadowWrapper meadow;

  public void init() {
    SimulationSettings settings = Simulation.getSettings();
    setDelay(settings.getTick());
    setBeeNum(settings.getBeesNum());
    setSwarm(new ArrayList<Bee>(getBeeNum()));
    setDuration(settings.getDuration());
    setRecording(settings.isRecord());
    if (isRecording()) {
      setRecorder(new Recorder());
      getRecorder().init(settings);
    }
    RandomGenerator.initialize(settings.getRandomSeed());

    // Create an environment
    Environment.reset();
    env = Environment.getInstance();
    // Create bees
    Class<? extends Bee> clazz = settings.getBeeClass();
    Constructor<? extends Bee> c = null;
    if (isPlaying()) {
      clazz = ReplayBee.class;
    }
    try {
      c = clazz.getConstructor(int.class);
    } catch (SecurityException e) {
      e.printStackTrace();
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    }

    try {
      for (int i = 0; i < getBeeNum(); i++) {
        swarm.add(c.newInstance(i));
      }
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }

    // Create a GUI window to show our bee
    if (settings.isGui()) {
      bv = new BeeViewer(swarm);
      bv.setDefaultCloseOperation(javax.swing.JFrame.HIDE_ON_CLOSE);
    }
  }

  public abstract void simulate();

  public abstract void results();

  public abstract void clean();

  public void pause() {
    PAUSED = !PAUSED;
  }

  public static boolean isPAUSED() {
    return PAUSED;
  }

  public int getBeeNum() {
    return beeNum;
  }

  public void setBeeNum(int beeNum) {
    this.beeNum = beeNum;
  }

  public List<Bee> getSwarm() {
    return swarm;
  }

  public void setSwarm(List<Bee> swarm) {
    this.swarm = swarm;
  }

  public Environment getEnv() {
    return env;
  }

  public void setEnv(Environment env) {
    this.env = env;
  }

  public BeeViewer getBv() {
    return bv;
  }

  public void setBv(BeeViewer bv) {
    this.bv = bv;
  }

  public Recorder getRecorder() {
    return recorder;
  }

  public void setRecorder(Recorder recorder) {
    this.recorder = recorder;
  }

  public boolean isRecording() {
    return recording;
  }

  public void setRecording(boolean recording) {
    this.recording = recording;
  }
  
  public boolean isPlaying() {
    return playing;
  }
  
  public void setPlaying(boolean playing) {
    this.playing = playing;
  }

  public Player getPlayer() {
    return player;
  }
  
  public void setPlayer(Player player) {
    this.player = player;
  }

  public synchronized int getDelay() {
    return delay;
  }

  public synchronized void setDelay(int delay) {
    this.delay = delay;
  }

  public synchronized int getDuration() {
    return duration;
  }

  public synchronized void setDuration(int duration) {
    this.duration = duration;
  }

  public MeadowWrapper getMeadow() {
    return meadow;
  }

  public void setMeadow(MeadowWrapper meadow) {
    this.meadow = meadow;
  }

  public GlobalState getGlobalState() {
    return globalState;
  }

  public void setGlobalState(GlobalState globalState) {
    this.globalState = globalState;
  }

}